﻿using LightCAD.Core;
using LightCAD.Core.Elements;
using LightCAD.Three;
using SkiaSharp;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace LightCAD.Runtime
{
    public class Model3DEditRuntime
    {
        private WebGLRenderer renderer;
        private Camera camera;
        private Scene scene;
        private Mesh boxMesh;
        private RenderCanvas canvas = new RenderCanvas();
        private OrbitControls controls;
        private Group noDrawingGroup;
        public DocumentRuntime DocRt { get; set; }
        public IModel3DEditControl Control { get; set; }

        public Model3DEditRuntime(DocumentRuntime docRt, IModel3DEditControl control)
        {
            DocRt = docRt;
            Control = control;
            this.Control.AttachEvents(this.OnInit, this.OnDestory, this.OnMouseEvent, this.OnKeyDown, this.OnKeyUp, this.OnSizeChanged, this.OnRender);
            this.DocRt.Document.ObjectChangedAfter += Document_ObjectChangedAfter;
            this.DocRt.Document.PropertyChangedAfter += Document_PropertyChangedAfter;
            this.DocRt.Action.SelectsChanged += DocumentAction_SelectsChanged;
        }
        private JsArr<Material> SelectMats=new JsArr<Material> { new MeshBasicMaterial(
            new MaterialValues { color=new Color(0x0000FF),transparent=true,opacity=0.5 })} ;
        private ConcurrentDictionary<int, JsArr<Material>> OriginMaterials = new ConcurrentDictionary<int, JsArr<Material>>();
        private void DocumentAction_SelectsChanged(object? sender, System.EventArgs e)
        {
            foreach (Mesh mesh in this.noDrawingGroup.children)
            {
                if (mesh == null)
                    continue;
                var ele= this.DocRt.Action.SelectedElements.Find(e => e.Id.ToString() == mesh.name);
                if (ele == null)
                    UnSelectMesh(mesh);
                else
                    SelectMesh(mesh);
            }
            this.Control.RefreshControl();
        }
        private void SelectMesh(Mesh mesh)
        {
            OriginMaterials.GetOrAdd(mesh.id, mesh.materials);
            mesh.materials = SelectMats;
        }
        private void UnSelectMesh(Mesh mesh)
        {
            if (mesh.materials == SelectMats)
                mesh.materials = OriginMaterials[mesh.id];
        }
        private bool updating;
        private void Document_PropertyChangedAfter(object? sender, PropertyChangedEventArgs e)
        {
            if (updating) return;
            updating = true;
            Task.Run(() =>
            {
                Thread.Sleep(10);
                this.OnUpdate();
                updating = false;
            });

        }
        private Task updateTask;
        private void Document_ObjectChangedAfter(object? sender, ObjectChangedEventArgs e)
        {
            if (updating) return;
            updating = true;
            void update()
            {
                Thread.Sleep(10);
                this.OnUpdate();
                updating = false;
            }
            Task.Run(update);
            if (false)
            {

                if (updateTask != null)
                    updateTask = updateTask.ContinueWith(
                        (t) => update()
                    );
                else
                    updateTask = Task.Run(() => update());
            }

        }

        internal void Dispose()
        {
            this.Control.DetachEvents(this.OnInit, this.OnDestory, this.OnMouseEvent, this.OnKeyDown, this.OnKeyUp, this.OnSizeChanged, this.OnRender);
        }
        private void OnInit()
        {
             this.canvas.Set((int)this.Control.Bounds.Width, (int)this.Control.Bounds.Height);

            this.scene = new Scene();
            var ambientLight = new AmbientLight(0xcccccc, 0.4);
            scene.add(ambientLight);

            var dirLight1 = new DirectionalLight(0xffffff, 0.8);
            dirLight1.position.set(1, 2, 3);
            scene.add(dirLight1);

            var dirLight2 = new DirectionalLight(0xffffff, 0.8);
            dirLight2.position.set(-1, -2, -3);
            scene.add(dirLight2);

            this.noDrawingGroup = new Group();
            this.noDrawingGroup.name = "无图元素组";
            scene.add(this.noDrawingGroup);

            var geometry = new BoxGeometry(200, 200, 200);
            //var material = new MeshBasicMaterial(new MaterialValues { color = new LightCAD.Three.Color(0xff0000) });
            var material = new MeshPhongMaterial(new MaterialValues { color = new Color(0xff0000), side = Three.Constants.DoubleSide });//map = map,

            this.boxMesh = new Mesh(geometry, material);
            //this.scene.add(this.boxMesh);
            var ratio = canvas.Width / canvas.Height;
            //camera = new PerspectiveCamera(70, ratio, 1, 20000);
            camera = new OrthographicCamera(-canvas.Width / 2, canvas.Width / 2, canvas.Height / 2, -canvas.Height / 2, 0.1, 200000);
            camera.quaternion.onChange(() => 
            {
                camera.rotation.setFromQuaternion(camera.quaternion, camera.rotation.order,true);
            });
            camera.rotation.onChange(() =>
            {
                if (Math.Abs(camera.rotation.x - Math.PI / 2) < 1e-3)
                    camera.up.set(0, -1, 0);
                else if (Math.Abs(camera.rotation.x + Math.PI / 2) < 1e-3)
                    camera.up.set(0, 1, 0);
                else camera.up.set(0, 0, 1);
                camera.updateProjectionMatrix();
            });
            camera.position.copy(syncCameraOffset);
            //camera.position.set(-5000, 5000, 5000);
            camera.zoom = 0.05;
            camera.up.set(0, 0, 1);
            camera.lookAt(new Vector3());

            var axesHelper = new AxesHelper(2000);
            scene.add(axesHelper);

            this.canvas = new RenderCanvas().Set(canvas.Width, canvas.Height);
            this.renderer = new WebGLRenderer(canvas);
            //renderer.setSize(glControl.Width, glControl.Height);
            controls = new OrbitControls(camera, this.Control);
            controls.screenSpacePanning = true;
            controls.addEventListener("change", (e) =>
            {
                this.Control.RefreshControl();
            });
        }
        private void OnDestory()
        {
            this.controls.dispose();
            this.renderer.dispose();
        }


        public void OnSizeChanged(Bounds bounds)
        {
            canvas.Set((int)bounds.Width, (int)bounds.Height);

            if (camera is PerspectiveCamera)
            {
                (camera as PerspectiveCamera).aspect = (double)this.canvas.Width / this.canvas.Height;
            }
            else
            {
                var oriCamera = camera as OrthographicCamera;
                oriCamera.left = -this.canvas.Width / 2.0;
                oriCamera.right = this.canvas.Width / 2.0;
                oriCamera.top = this.canvas.Height / 2.0;
                oriCamera.bottom = -this.canvas.Height / 2.0;
            }
            camera.updateProjectionMatrix();

            //glControl.Width = this.Width;
            //glControl.Height = this.Height;
            //this.canvas.Set(canvas.Width, canvas.Height);
            this.renderer.setViewport(0, 0, canvas.Width, canvas.Height);

            //this.Control.RefreshControl();
        }



        #region Events

        internal virtual void OnMouseEvent(string type, MouseEventRuntime e)
        {
            if (type == "Down") OnMouseDown(e);
            else if (type == "Move") OnMouseMove(e);
            else if (type == "Up") OnMouseUp(e);
            else if (type == "Wheel") OnMouseWheel(e);

        }
        private void OnMouseDown(MouseEventRuntime e)
        {

        }

        #region Hover

        private SKPoint preHover;
        private bool isPointerHovering;
        private System.Timers.Timer hoverTimer;
        private void HoverTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
        {
            hoverTimer.Stop();
            this.isPointerHovering = true;
            HoverEventStart();
        }
        private void HoverEventStart()
        {

        }
        private void HoverEventEnd()
        {

        }

        #endregion

        private void OnMouseMove(MouseEventRuntime e)
        {

            //VPort.ShowFloatBlock(this);

            //lblDrawCount.Content = VPort.DrawCounter + "/" + doc.ModelSpace.Elements.Count;

        }

        private void OnMouseUp(MouseEventRuntime e)
        {

        }

        private void OnMouseWheel(MouseEventRuntime e)
        {

        }

        internal virtual void OnKeyDown(KeyEventRuntime e)
        {
            if (e.KeyCode == 17)//Ctrl
            {

            }
            else if (e.KeyCode == 27)//ESC
            {
            }

            if (e.KeyModifiers == LcKeyModifiers.Control)
            {

            }
            else if (e.KeyCode == 46)//Delete
            {
                CommandCenter.Instance.Execute("ERASE");
            }

            //处理在位输入及Esc/Copy，Cut/Paste将启动相应Action
        }

        internal virtual void OnKeyUp(KeyEventRuntime e)
        {
            if (e.KeyCode == 17)//Ctrl
            {

            }

        }

        #endregion

        public void OnUpdate()
        {
            Debug.Print("Update:" + DateTime.Now);

            var doc = this.DocRt.Document;

            var result = ElementFilter.GroupByDrawings(doc.ModelSpace.Elements);
            if (result.Item1.Count > 0)
            {
                var usedDrawings = new List<LcDrawing>();
                foreach (var building in doc.Buildings)
                {
                    var used = CreateBuilding(building, result.Item1);
                    usedDrawings.AddRange(used);
                }
                foreach (var drawing in result.Item1.Keys)
                {
                    if (usedDrawings.Contains(drawing)) continue;
                    CreateDrawing(drawing, result.Item1[drawing]);
                }
            }

            if (result.Item2.Count > 0)
            {
                CreateNoDrawingElements(result.Item2);
            }

            this.Control.RefreshControl();
        }
        private static MeshPhongMaterial WallMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xc0c0c0) });
        private static MeshPhongMaterial DoorMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xffcc66) });
        private static MeshPhongMaterial DoorKnotMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0x669900)});
        private static MeshPhongMaterial WindowMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0x333300)});
        private static MeshPhongMaterial WinGlassMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xb3d9ff), opacity= 0.3 ,transparent=true});
        private static MeshPhongMaterial LadderMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xe8e1d1) });
        private static MeshPhongMaterial PlatMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xfbf9f3) });
        private static MeshPhongMaterial DefaultMat = new MeshPhongMaterial(new MaterialValues
        { color = new Color(0xBEBEBE) });
        private void CreateNoDrawingElements(List<LcElement> elements)
        {
            var eles = elements.Where(e => e is IElement3D).ToList();
            if (eles.Count == 0) return;

            this.noDrawingGroup.removeAll();
            foreach (var ele in eles)
            {
                var eleAction = ele.RtAction as ElementAction;
                var geoArr = eleAction.CreateGeometry3D(ele, Matrix3d.Identity);
                var meshes = new JsArr<Mesh>();
                if (ele.Type.Name == "Wall")
                {
                    var mesh = new Mesh(geoArr[0], WallMat);
                    meshes.add(mesh);
                }
                else if (ele.Type.Name == "Door")
                {
                    var mesh = new Mesh(geoArr[0], DoorMat);
                    meshes.add(mesh);
                    var meshKnot = new Mesh(geoArr[1], DoorKnotMat);
                    meshes.add(meshKnot);
                }
                else if (ele.Type.Name == "Window")
                {
                    var mesh = new Mesh(geoArr[0], WindowMat);
                    meshes.add(mesh);
                    var meshGlass = new Mesh(geoArr[1], WinGlassMat);
                    meshes.add(meshGlass);
                }
                else if (ele.Type.Name == "Stair")
                {
                    var mesh = new Mesh(geoArr[0], LadderMat);
                    meshes.add(mesh);
                    mesh = new Mesh(geoArr[1], LadderMat);
                    meshes.add(mesh);
                    mesh = new Mesh(geoArr[2], PlatMat);
                    meshes.add(mesh);
                }
                else if (ele.Type.Name == "Slab")
                {
                    var mesh = new Mesh(geoArr[0], DefaultMat);
                    meshes.add(mesh);
                }
                for (int i = 0; i < meshes.length; i++)
                {
                    var mesh = meshes[i];
                    mesh.name=ele.Id.ToString();
                    if (ele.IsSelected)
                    {
                        SelectMesh(mesh);
                    }
                }
               
                this.noDrawingGroup.add(meshes.ToArray());
            }
        }

        private void CreateDrawing(LcDrawing drawing, List<LcElement> lcElements)
        {
        }

        private List<LcDrawing> CreateBuilding(LcBuilding building, Dictionary<LcDrawing, List<LcElement>> item1)
        {
            return new List<LcDrawing>();
        }

        internal virtual void OnRender()
        {
            this.renderer.setClearColor(0xeeeeee);
            this.renderer.render(this.scene, camera);
        }
        private readonly Vector3 syncCameraOffset= new Vector3(-10000,-10000,50000);
        public virtual void SyncViewPort2D(ViewportRuntime  viewportRt) 
        {
            if (this.camera is OrthographicCamera)
            {
                var vc = viewportRt.DcsToWcs.MultiplyPoint(new Vector2d(0, 0));
                var scale = viewportRt.Viewport.Scale;
                var orCamera= (OrthographicCamera)this.camera;
                orCamera.zoom = scale/2*((double)canvas.Width/viewportRt.Viewport.Width);
                orCamera.position.addVectors(new Vector3(vc.X,vc.Y,0), syncCameraOffset);
                var target = new Vector3(vc.X, vc.Y, 0);
                //orCamera.up.set(0, 1, 0);
                orCamera.lookAt(target);
                controls.target.copy(target);
            }
            this.Control.RefreshControl();
        }

    }
}
